package cn.snmp.mutual.org.opennms.protocols.ip;

import cn.snmp.mutual.org.opennms.protocols.exceptions.InsufficientDataException;


public class IPHeader {
    private byte m_version;
    private byte m_hdrlen;
    private byte m_tos;
    private short m_length;
    private short m_identity;
    private byte m_flags;
    private short m_fragOffset;
    private byte m_ttl;
    private byte m_protocol;
    private short m_checksum;
    private int m_srcAddr;
    private int m_dstAddr;
    private byte[] m_options;

    private byte[] dup(final byte[] src) {
        byte[] cpy = null;
        if (src != null) {
            cpy = new byte[src.length];
            for (int x = 0; x < src.length; ++x) {
                cpy[x] = src[x];
            }
        }
        return cpy;
    }

    private static short byteToShort(final byte b) {
        short r = b;
        if (r < 0) {
            r += 256;
        }
        return r;
    }

    private static int byteToInt(final byte b) {
        int r = b;
        if (r < 0) {
            r += 256;
        }
        return r;
    }

    private static int shortToInt(final short s) {
        int r = s;
        if (r < 0) {
            r += 65536;
        }
        return r;
    }

    public IPHeader(final IPHeader second) {
        this.m_hdrlen = second.m_hdrlen;
        this.m_version = second.m_version;
        this.m_tos = second.m_tos;
        this.m_length = second.m_length;
        this.m_identity = second.m_identity;
        this.m_flags = second.m_flags;
        this.m_fragOffset = second.m_fragOffset;
        this.m_ttl = second.m_ttl;
        this.m_protocol = second.m_protocol;
        this.m_checksum = second.m_checksum;
        this.m_srcAddr = second.m_srcAddr;
        this.m_dstAddr = second.m_dstAddr;
        this.m_options = this.dup(second.m_options);
    }

    public IPHeader(final byte[] header, final int offset) {
        final int length = header.length;
        if (length - offset < 20) {
            throw new InsufficientDataException("Minimum IP header size is 20 bytes");
        }
        int ndx = 0;
        this.m_version = (byte) (header[offset + ndx] >>> 4);
        this.m_hdrlen = (byte) (header[offset + ndx] & 0xF);
        ++ndx;
        if (this.m_version != 4) {
            throw new UnknownIPVersionException("Unknown IP Version, version = " + this.m_version);
        }
        if (length - offset < this.m_hdrlen * 4) {
            throw new InsufficientDataException("Insufficient data: buffer size = " + (length - offset) + " and header length = " + this.m_hdrlen * 4);
        }
        this.m_tos = header[offset + ndx];
        ++ndx;
        this.m_length = (short) (byteToShort(header[offset + ndx]) << 8 | byteToShort(header[offset + ndx + 1]));
        ndx += 2;
        this.m_identity = (short) (byteToShort(header[offset + ndx]) << 8 | byteToShort(header[offset + ndx + 1]));
        ndx += 2;
        this.m_fragOffset = (short) (byteToShort(header[offset + ndx]) << 8 | byteToShort(header[offset + ndx + 1]));
        this.m_flags = (byte) (this.m_fragOffset >>> 13);
        this.m_fragOffset &= 0x1FFF;
        ndx += 2;
        this.m_ttl = header[offset + ndx];
        ++ndx;
        this.m_protocol = header[offset + ndx];
        ++ndx;
        this.m_checksum = (short) (byteToShort(header[offset + ndx]) << 8 | byteToShort(header[offset + ndx + 1]));
        ndx += 2;
        this.m_srcAddr = (byteToInt(header[offset + ndx]) << 24 | byteToInt(header[offset + ndx + 1]) << 16 | byteToInt(header[offset + ndx + 2]) << 8 | byteToInt(header[offset + ndx + 3]));
        ndx += 4;
        this.m_dstAddr = (byteToInt(header[offset + ndx]) << 24 | byteToInt(header[offset + ndx + 1]) << 16 | byteToInt(header[offset + ndx + 2]) << 8 | byteToInt(header[offset + ndx + 3]));
        ndx += 4;
        final int hl = byteToInt(this.m_hdrlen) << 2;
        if (hl > ndx) {
            this.m_options = new byte[hl - ndx];
            for (int x = 0; ndx < hl; this.m_options[x++] = header[offset + ndx++]) {
            }
        } else {
            this.m_options = null;
        }
    }

    public byte getVersion() {
        return this.m_version;
    }

    public int getHeaderLength() {
        return 4 * byteToInt(this.m_hdrlen);
    }

    public byte getTypeOfService() {
        return this.m_tos;
    }

    public boolean getTypeOfService(final int bit) {
        return bit >= 0 && bit < 8 && (this.m_tos & 1 << bit) != 0x0;
    }

    public int getPacketLength() {
        return shortToInt(this.m_length);
    }

    public short getIdentity() {
        return this.m_identity;
    }

    public byte getFlags() {
        return this.m_flags;
    }

    public boolean getFlag(final int bit) {
        return bit >= 0 && bit < 3 && (this.m_flags & 1 << bit) != 0x0;
    }

    public short getFragmentOffset() {
        return this.m_fragOffset;
    }

    public byte getTTL() {
        return this.m_ttl;
    }

    public byte getProtocol() {
        return this.m_protocol;
    }

    public short getChecksum() {
        return this.m_checksum;
    }

    public int getSourceAddress() {
        return this.m_srcAddr;
    }

    public int getDestinationAddress() {
        return this.m_dstAddr;
    }

    public byte[] getOptionData() {
        return this.m_options;
    }

    public static String addressToString(final int ipv4Addr) {
        final StringBuffer buf = new StringBuffer();
        buf.append(ipv4Addr >> 24 & 0xFF);
        buf.append('.');
        buf.append(ipv4Addr >> 16 & 0xFF);
        buf.append('.');
        buf.append(ipv4Addr >> 8 & 0xFF);
        buf.append('.');
        buf.append(ipv4Addr & 0xFF);
        return buf.toString();
    }

    public static String addressToString(final byte[] buf) {
        if (buf.length != 4) {
            throw new IllegalArgumentException("IPv4 Address must be 4-bytes in length");
        }
        final int a = (buf[0] < 0) ? (buf[0] + 256) : buf[0];
        final int b = (buf[1] < 0) ? (buf[1] + 256) : buf[1];
        final int c = (buf[2] < 0) ? (buf[2] + 256) : buf[2];
        final int d = (buf[3] < 0) ? (buf[3] + 256) : buf[3];
        final StringBuffer sbuf = new StringBuffer();
        sbuf.append(a).append('.').append(b).append('.').append(c).append('.').append(d);
        return sbuf.toString();
    }
}
